package com.study.crypto.basic.utils;

import com.study.crypto.basic.bean.KeyValue;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.KeyPair;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author Songjin
 * @since 2020-12-30 18:52
 */
public final class CertUtils {
    
    private CertUtils() {
    }
    
    /**
     * 组装 dn
     * @param countryName       证书C项
     * @param organizationName  证书O项
     * @param commonName        证书CN项
     * @return dn
     */
    public static X500Name buildDistinctName(String countryName, String organizationName, String commonName) {
        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
        builder.addRDN(BCStyle.C, countryName);
        builder.addRDN(BCStyle.O, organizationName);
        builder.addRDN(BCStyle.CN, commonName);
        return builder.build();
    }

    /**
     * 将证书 dn 字符串转换成 X500Name
     * @param dn 将证书 dn 字符串
     * @return X500Name
     */
    public static X500Name buildDistinctName(String dn) {
        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
        Map<ASN1ObjectIdentifier, String> dnMap = CertUtils.splitDn(dn);
        for (Map.Entry<ASN1ObjectIdentifier, String> entry : dnMap.entrySet()) {
            builder.addRDN(entry.getKey(), entry.getValue());
        }
        return builder.build();
    }

    /**
     * 将证书 DN 转换成 Map<oid,String> 映射
     * @param dn DN字符串
     * @return Map<oid,String> 映射
     */
    public static Map<ASN1ObjectIdentifier, String> splitDn(String dn) {
        // 先删除字符串中的空白字符
        String dnTrim = StringUtils.deleteWhitespace(dn);
        return Arrays.stream(dnTrim.split(","))
                     .map(textItem -> textItem.trim().split("="))
                     .map(itemSplit -> {
                         KeyValue<ASN1ObjectIdentifier, String> keyValue = new KeyValue<>();
                         keyValue.setKey(BCStyle.INSTANCE.attrNameToOID(itemSplit[0]));
                         keyValue.setValue(itemSplit[1]);
                         return keyValue;
                     }).collect(Collectors.toMap(KeyValue::getKey, KeyValue::getValue));
    }
    
    /**
     * 签发终端用户证书
     * @param publicKeyB64  公钥base64
     * @param subject       证书主体
     * @param signer        签名
     * @param issuer        颁发者
     * @param notBefore     证书有效起始时间
     * @param notAfter      证书有效截止时间
     * @param serialNumber  证书序列号
     * @return
     * @throws Exception
     */
    public static Certificate generateUserCertificate(String publicKeyB64, X500Name subject, ContentSigner signer, Certificate issuer, LocalDateTime notBefore, LocalDateTime notAfter, BigInteger serialNumber) throws Exception {
        return issueCertificate(true, publicKeyB64, subject, signer, issuer, notBefore, notAfter, serialNumber);
    }
    
    /**
     * 签发终端用户加密证书
     * @param publicKeyB64  公钥base64
     * @param subject       证书主体
     * @param signer        签名
     * @param issuer        颁发者
     * @param notBefore     证书有效起始时间
     * @param notAfter      证书有效截止时间
     * @param serialNumber  证书序列号
     * @return
     * @throws Exception
     */
    public static Certificate generateUserEncCertificate(String publicKeyB64, X500Name subject, ContentSigner signer, Certificate issuer, LocalDateTime notBefore, LocalDateTime notAfter, BigInteger serialNumber) throws Exception {
        return issueCertificate(false, publicKeyB64, subject, signer, issuer, notBefore, notAfter, serialNumber);
    }
    
    /**
     * 签发终端用户加密证书
     * @param keyPair       公私钥对
     * @param subject       证书主体
     * @param signer        签名
     * @param issuer        颁发者
     * @param notBefore     证书有效起始时间
     * @param notAfter      证书有效截止时间
     * @param serialNumber  证书序列号
     * @return
     * @throws Exception
     */
    public static Certificate generateUserEncCertificate(KeyPair keyPair, X500Name subject, ContentSigner signer, Certificate issuer, LocalDateTime notBefore, LocalDateTime notAfter, BigInteger serialNumber) throws Exception {
        BCECPublicKey publicKey = (BCECPublicKey) keyPair.getPublic();
        byte[] publicKeyBytes = publicKey.getQ().getEncoded(false);
        return issueCertificate(false, Base64.encodeBase64String(publicKeyBytes), subject, signer, issuer, notBefore, notAfter, serialNumber);
    }
    
    private static Certificate issueCertificate(boolean signOrEnc, String publicKeyB64, X500Name subject, ContentSigner signer, Certificate issuer, LocalDateTime notBefore, LocalDateTime notAfter, BigInteger serialNumber) throws Exception {
        Date notBefore_ = EssPdfUtil.of(notBefore);
        Date notAfter_  = EssPdfUtil.of(notAfter);
        ASN1ObjectIdentifier oid = signer.getAlgorithmIdentifier().getAlgorithm();
        SubjectPublicKeyInfo subjectKeyInfo = KeyUtils.obtainSubjectPublicKeyInfoBase64(oid, publicKeyB64);
        BcX509ExtensionUtils extUtils = new BcX509ExtensionUtils();
        X509v3CertificateBuilder builder = new X509v3CertificateBuilder(issuer.getSubject(), serialNumber, notBefore_, notAfter_, subject, subjectKeyInfo);
        builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false));
        int usage;
        if (signOrEnc) {
            usage = KeyUsage.digitalSignature | KeyUsage.nonRepudiation;
        } else {
            usage = KeyUsage.keyEncipherment | KeyUsage.dataEncipherment | KeyUsage.keyAgreement;
        }
        builder.addExtension(Extension.keyUsage, true, new KeyUsage(usage));
        // 授权密钥标识
        AuthorityKeyIdentifier authKeyId = extUtils.createAuthorityKeyIdentifier(issuer.getSubjectPublicKeyInfo());
        builder.addExtension(Extension.authorityKeyIdentifier, false, authKeyId);
        SubjectKeyIdentifier subjectKeyIdentifier = extUtils.createSubjectKeyIdentifier(subjectKeyInfo);
        // 使用者密钥标识
        builder.addExtension(Extension.subjectKeyIdentifier, false, subjectKeyIdentifier);
        X509CertificateHolder certificateHolder = builder.build(signer);
        return certificateHolder.toASN1Structure();
    }
    
    /**
     * 判断证书是否是签名证书
     * @param certBytes 证书字节数据
     * @return
     */
    public static boolean usageDigitalSignature(byte[] certBytes) throws CertificateException, NoSuchProviderException {
        CertificateFactory instance    = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
        X509Certificate    certificate = (X509Certificate) instance.generateCertificate(new ByteArrayInputStream(certBytes));
        boolean[] keyUsage = certificate.getKeyUsage();
        return keyUsage[0];
    }
    
    /**
     * 从字节数组构建证书对象
     * @param certBytes 证书字节数组
     * @return
     * @throws IOException
     */
    public static Certificate rebuildCertificate(byte[] certBytes) throws IOException {
        X509CertificateHolder holder = new X509CertificateHolder(certBytes);
        return holder.toASN1Structure();
    }
    
    /**
     * 从字节数组构建证书对象
     * @param certBytes 证书字节数组
     * @return
     * @throws CertificateException
     * @throws NoSuchProviderException
     */
    public static X509Certificate rebuildX509Certificate(byte[] certBytes) throws CertificateException, NoSuchProviderException {
        CertificateFactory instance = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
        return (X509Certificate) instance.generateCertificate(new ByteArrayInputStream(certBytes));
    }
    
    /**
     * 从证书中解析 KeyUsage 整数值
     * @param certBytes 证书字节数组
     * @return
     * @throws IOException
     */
    public static int certificateUsage(byte[] certBytes) throws IOException {
        Certificate certificate = rebuildCertificate(certBytes);
        Extensions  extension   = certificate.getTBSCertificate().getExtensions();
        KeyUsage keyUsage = KeyUsage.fromExtensions(extension);
        byte[] byteArr = new byte[4];
        Arrays.fill(byteArr, (byte) 0);
        byte[] usageBytes = keyUsage.getBytes();
        System.arraycopy(usageBytes, 0, byteArr, 0, usageBytes.length);
        ByteBuffer byteBuf = ByteBuffer.allocate(4);
        byteBuf.put(byteArr);
        byteBuf.order(ByteOrder.LITTLE_ENDIAN);
        return byteBuf.getInt(0);
    }
}